#include <iostream>
#include <mpi.h>
#include <Magick++.h>
#include <string>
#include "functions.hpp"
#include "usual.hpp"
//#include <stdlib.h>


using namespace std;
using namespace MPI;
using namespace Magick;
using namespace MagickCore;


int main(int argc, char* argv[])
{

    if(argc != 2 )
    {
        string cmd("wrong arguments ");
        usage(cmd);
    }

    MPI::Init();
    int rank       = MPI::COMM_WORLD.Get_rank();
    int size_world = MPI::COMM_WORLD.Get_size();
    Usual* usuel = NULL;

/// on ne passe pas par mpi si 1 seul processus
    if(size_world < 2)
            rootFunction(usuel, argv[argc-1]);
    else
    {
if(rank == 0){
        time_t tim=time(NULL);
        char *s=ctime(&tim);
        s[strlen(s)-1]=0;

        cout << "\tHi, what's a wonderfull day :)  " << s << endl << endl;
}

        int matrix_size = 0;
        double **matrix;

    ///lecture du fichier input
        if(rank == 0) {
            usuel = new Usual(argv[argc-1]);
            matrix_size = usuel->get_matrix_size();
         } else {
            usuel = new Usual();
         }
    ///envoi de la taille de la matrice à tout le monde pour pouvoir transmettre le noyau
        COMM_WORLD.Bcast(&matrix_size, 1, MPI::INT, 0);
        if(rank == 0 ) cout << "\t#"<< rank << " matrix size: "<< matrix_size << endl;

    ///envoi de la matrice à tout le monde pour pouvoir convoluer
        matrix = (double**) calloc(matrix_size, sizeof(double *));
        for (int i = 0; i < matrix_size; i++)
            matrix[i] = (double*) calloc(matrix_size, sizeof(double));

        if(rank == 0){  matrix = usuel->get_matrix(); }

        try {
            for(int i = 0; i < matrix_size; ++i)
                COMM_WORLD.Bcast(matrix[i], matrix_size, MPI::DOUBLE , 0);
        } catch(Error &lErr){
            free(matrix);
            cerr << lErr.what() << endl;
            abort("erreur du 2ème Bcast");
        }

        int * cpt = new int();
        if(rank == 0) *cpt = usuel->get_picture_list()->size();
        COMM_WORLD.Bcast(cpt, 1, MPI::INT, 0);

    ///affichage matrice
//        for(int i = 0; i < matrix_size; ++i)
//            for(int j = 0; j < matrix_size; ++j)
//                cout << endl << "\tproc #" << rank << " --- " << matrix[i][j];cout <<endl;
//
double initTime, interTime, finalTime;

     while(*cpt != 0)
     {
         initTime = MPI::Wtime();


//            cout << "---------" << rank <<" début des allocations -----------------" << endl << endl;

        /// --------------------------------------------------------------
        /// --------------------- Init Traitement ------------------------
        /// --------------------------------------------------------------
            int *rows = (int*) malloc(sizeof(int));
            int *columns= (int*) malloc(sizeof(int));
            int middle_matrix, recvcount, total_pix;
            int* sendCounts, *displs;
            MagickCore::PixelPacket *pix_cache;
            MagickCore::PixelPacket *pix_cache_final;
            PixelPacket* sendArray;
            PixelPacket* rcvArray;
            Magick::Image res_local;
            Magick::Image res;
            Magick::Image lImage;
            int part_data;
            MPI::Datatype myType;
            myType = MPI::BYTE.Create_vector(1, sizeof(PixelPacket), sizeof(PixelPacket));
            myType.Commit();

                if(rank == 0)
                {
                    lImage = usuel->pop_picture(); // récupère la prochaine image à traiter
                    *rows    = lImage.rows();
                    *columns = lImage.columns();
                }

                COMM_WORLD.Bcast(rows, 1, MPI::INT, 0);
                COMM_WORLD.Bcast(columns, 1, MPI::INT, 0);
                middle_matrix = ((matrix_size - 1) / 2) * (*columns) ; // lignes en plus pour la bordure nécessaire
                sendCounts = (int*)calloc(size_world, sizeof(int)); // tout le monde alloue le tableau
                displs = (int*) calloc(size_world, sizeof(int));
                total_pix = (*rows) * (*columns);
                part_data = ((*rows / size_world) * (*columns)) + (*columns);
//                cout << "début init tableau du proc "<<rank << endl;

            ///envoi des données pour chaque processus avec la bordure
                sendCounts[0] = part_data + middle_matrix;
                for(int i =1; i<size_world-1;++i)
                    sendCounts[i] = part_data + 2*middle_matrix;
                sendCounts[size_world-1] = total_pix - ((size_world-1)*part_data) + middle_matrix;


            ///recieve count
                recvcount = sendCounts[rank];

            ///calcul du displacement (à partir de 0 à chaque fois)
                displs[0] = 0;
                for(int i =1; i< size_world; ++i)
                    displs[i] = (part_data *i ) - middle_matrix;

                sendArray = (PixelPacket*) malloc(total_pix* sizeof(PixelPacket));
                rcvArray = (PixelPacket*) calloc(sendCounts[rank], sizeof(PixelPacket));

                if(rank == 0 )
                {
                    int cpt = 0;
                    for(int i = 0; i< (*rows); ++i)
                        for(int j = 0; j< (*columns); ++j){
                            sendArray[cpt] = lImage.pixelColor(j,i); // affiche des données "brouillard" pour tout le monde
                            ++cpt;

                        }
//                    cout << "\t\t\tsendArray alloué à: "<< sendArray << endl;

                }

//                cout << "---------" << rank <<" PASS allocations -----------------" << endl << endl;


            /// --------------------------------------------------------------
            /// ------------------------ Scatterv ----------------------------
            /// --------------------------------------------------------------
                try
                {
                    COMM_WORLD.Scatterv(sendArray, sendCounts, displs, myType,
                                        rcvArray, recvcount, myType, 0);
                }
                catch(Error &lErr)
                {
                    free(sendCounts);
                    free(displs);
                    free(sendArray);
                    free(rcvArray);
                    delete rows;
                    delete columns;
                    for (int i = 0; i < matrix_size; i++)
                        free(matrix[i]);
                    free(matrix);
                    cerr << lErr.what() << endl;
                    abort("erreur du ScatterV");
                }
//                cout << "#"<<rank << " passe Scatterv " << endl;
            /// --------------------------------------------------------------
            /// --------------------- FIN Scatterv ---------------------------
            /// --------------------------------------------------------------




                /// Tout le monde
                int rows_res = recvcount / (*columns);
                ssize_t fin_i = rows_res;//- 2*(matrix_size /2);// *rows / size_world + *columns;//rows - matrix_size; //indic lignes (vertical)
                ssize_t fin_j = *columns;// - matrix_size+1; //indic colonnes (horizontal)


//                cout << "#"<<rank << " rows original " << *rows << endl;
//                cout << "#"<<rank << " columns original " << *columns << endl;
//                cout << "#"<<rank << " rows_resultat from recvcount " << rows_res << endl;
//                cout << "#"<<rank << " rows_resultat " << fin_i << endl;
//                cout << "#"<<rank << " columns_resultat " << fin_j << endl;

                Geometry geom(fin_j, fin_i);
                res_local  = Magick::Image(geom, Color("white"));
                res_local.modifyImage();
                Pixels my_res_cache(res_local);
                pix_cache = my_res_cache.get(0,0,fin_j,fin_i);

//                cout << "#"<<rank << " correctly init cache pix " << endl;

            /// --------------------------------------------------------------
            /// ---------------------- CONVOLUTION ---------------------------
            /// --------------------------------------------------------------
                for(int i =0; i< fin_j*fin_i ;++i)
                {
                    Color col("white");
                    Quantum blue(0), red(0), green(0);
                    for(int y = 0; y<  matrix_size ; ++y)
                    {
                        for(int x = 0; x < matrix_size;++x)
                        {
                            blue  += rcvArray[i+x+y].blue  * matrix[x][y];
                            red   += rcvArray[i+x+y].red   * matrix[x][y];
                            green += rcvArray[i+x+y].green * matrix[x][y];
                        }
                    }
                    col.alphaQuantum(0);
                    col.redQuantum( red );
                    col.greenQuantum( green );
                    col.blueQuantum( blue);
                    if(Color("black") != col);
                        *pix_cache++=col;
                }
                my_res_cache.sync();
               //res_local.display();
                PixelPacket* send_res = my_res_cache.get(0,0,fin_j,fin_i);


//                cout << "#"<<rank << "\t\tinit gatherV " << endl;

            /// --------------------------------------------------------------
            /// ------------------------- Gatherv ----------------------------
            /// --------------------------------------------------------------
                    int taille_fin = ((*rows)-matrix_size/2)*((*columns)-matrix_size/2);
                    pix_cache_final = (PixelPacket*) calloc(taille_fin, sizeof(PixelPacket));
                    PixelPacket* var;
                    Pixels* my_res_final_cache;
                    if(rank == 0)
                    {
                        Geometry g(*columns, *rows);
                        res = Magick::Image(g, Color("red"));
                        res.modifyImage();
                        my_res_final_cache = new Pixels(res);
                        pix_cache_final = my_res_final_cache->get(0,0,g.width(),g.height());
                    }

                    int nb_envoi = recvcount;
                    int *recoit = new int[size_world];
                    int *recoitdsp = new int[size_world];
                    int *taille_perso = new int(fin_j*fin_i);

//                    cout << "#"<<rank << "\trecoit["     << rank << "]= " << *taille_perso << endl;

                        MPI::COMM_WORLD.Gather(taille_perso, 1, MPI::INT, recoit, 1, MPI::INT, 0);

                    if(rank==0)
                    {
                        recoitdsp[0] = 0;
                        for(int i = 1; i< size_world; ++i)
                            recoitdsp[i] = recoit[i-1]+recoitdsp[i-1]-(matrix_size-1)*(*columns);
//
//                        for(int i = 0; i< size_world; ++i)
//                             cout << "#"<<rank << "\t  recoit["     << i << "]= " << recoit[i] << endl;
//                        for(int i = 0; i< size_world; ++i)
//                            cout << "#"<<rank << "\t  recoitdisp["     << i << "]= " << recoitdsp[i] << endl;

                    }

                    MPI::Datatype myType2;
                    myType2 = MPI::BYTE.Create_vector((*taille_perso)-(matrix_size-1), fin_j*sizeof(PixelPacket), fin_j*sizeof(PixelPacket));
                    myType2.Commit();

//                    cout << "#"<<rank << "\t\tAttent le GatherV " << endl;
                    MPI::COMM_WORLD.Gatherv(send_res, (*taille_perso), myType, pix_cache_final, recoit, recoitdsp, myType, 0);
//                    cout << "#"<<rank << "\t\tGatherV passé " << endl;

            free(sendCounts);
            free(displs);
            if(rank != 0) free(sendArray);
            free(rcvArray);


                    if(rank == 0)
                    {
                        my_res_final_cache->sync();
                        res.magick("png");
                        interTime = MPI::Wtime() - initTime;
                        usuel->addProcessTime(interTime);
                        cout << endl << endl << "\t\tTraitement de l'image "<< *cpt <<" en " << interTime << "secondes." <<endl;
                        cout  << "\t\t on a accordé "<< usuel->getProcessTime()  <<  "secondes de traitements en tout" <<endl;


                        initTime = MPI::Wtime();
                        res.write(usuel->get_write_path()+usuel->next_picture_string()+".png");
                        interTime = MPI::Wtime() - initTime;
                        usuel->pop_picture_string();
                        usuel->addWriteTime(interTime);

                        cout << endl << endl << "\t\técriture de l'image "<< *cpt <<" en " << interTime << "secondes." <<endl;
                        cout  << "\t\t on a accordé "<< usuel->getWriteTime()  <<  "secondes d'écriture en tout" <<endl;
//                        res.display();
                    }


            free(rows);
            free(columns);
            delete recoit;
            delete recoitdsp;
            delete taille_perso;
                        --(*cpt);
             }

          /// Liberation de la mémoire
            for (int i = 0; i < matrix_size; i++)
                free(matrix[i]);
            free(matrix);
            delete cpt;


        cout << "\t\tI'm processus "<< rank << " and i FINISHED"<< endl;

        if(rank == 0 )
            cout << endl << endl << "Total Time to process pictures = " << usuel->getTotalTime() << " secondes" << endl << endl;
        delete usuel;
    }

        MPI::Finalize();
        cout << "all process were well executed"  << endl;
        return 0;

}//*/
